//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#pragma once

#include "WeCorePrerequisites.h"

namespace WackiEngine
{
	// forward declaration
	class Texture;
		class Texture2D;
		class TextureCube;

	class Framebuffer
	{
	public:
		Framebuffer(GLenum target = GL_FRAMEBUFFER);
		~Framebuffer();

		// accessors
		void			setSize(GLsizei width, GLsizei height);

		const Texture*	attachment(GLenum attachment);
		GLuint			handle() const;
		GLenum			status() const;

		GLsizei			width() const;
		GLsizei			height() const;
		const GLsizei*	size() const;
		GLfloat			texelWidth() const;
		GLfloat			texelHeight() const;
		const GLfloat*	texelSize() const;

		// services
		void			bind(GLenum target = GL_FRAMEBUFFER) const;
		void			bindRead() const;
		void			bindDraw() const;
		static void		bindNone(GLenum target = GL_FRAMEBUFFER);

		void			attach(const Texture2D* texture, GLenum attachment, GLint level = 0);
		void			attach(const TextureCube* texture, GLenum face, GLenum attachment, GLint level = 0);

	private:
		// opengl handle
		GLuint			_handle;
		// framebuffer status
		GLenum			_status;
		// min of all attached textures
		GLsizei			_size[2];
		// size of a texel 1/_size
		GLfloat			_texelSize[2];

		// active attachments for this buffer
		struct TextureAttachment;
		Vector<TextureAttachment>	_attachments;
		GLsizei						_maxAttachments;

		// active draw buffers
		Vector<GLenum>				_drawBuffers;
		GLint						_maxDrawBuffers;


		// init the framebuffer, retrieve max values for attachments and drawbuffers
		void init();

		void addTextureAttachment(const TextureAttachment &texa);
		// activate the draw buffers based on this framebuffer
		void updateDrawBuffers();
		// update the framebuffers size based on the attachments
		void updateSize();
		// retrieve the GL framebuffer status
		void updateStatus();
	};

	// inline functions
	inline void Framebuffer::setSize(GLsizei width, GLsizei height)
	{
		_size[0] = width;
		_size[1] = height;
		_texelSize[0] = 1.0f / (GLfloat)_size[0];
		_texelSize[1] = 1.0f / (GLfloat)_size[1];
	}

	inline GLuint Framebuffer::handle() const
	{
		return _handle;
	}

	inline GLenum Framebuffer::status() const
	{
		return _status;
	}

	inline GLsizei Framebuffer::width() const
	{
		return _size[0];
	}

	inline GLsizei Framebuffer::height() const
	{
		return _size[1];
	}

	inline const GLsizei* Framebuffer::size() const
	{
		return _size;
	}

	inline GLfloat Framebuffer::texelWidth() const
	{
		return _texelSize[0];
	}

	inline GLfloat Framebuffer::texelHeight() const
	{
		return _texelSize[1];
	}

	inline const GLfloat* Framebuffer::texelSize() const
	{
		return _texelSize;
	}

	inline void Framebuffer::bind(GLenum target) const
	{
		assert(	target == GL_FRAMEBUFFER ||
				target == GL_READ_FRAMEBUFFER ||
				target == GL_DRAW_FRAMEBUFFER );

		glBindFramebuffer(target, _handle);
	}

	inline void Framebuffer::bindRead() const
	{
		bind(GL_READ_FRAMEBUFFER);
	}

	inline void Framebuffer::bindDraw() const
	{
		bind(GL_DRAW_FRAMEBUFFER);
	}

	inline void Framebuffer::bindNone(GLenum target)
	{
		assert(	target == GL_FRAMEBUFFER ||
				target == GL_READ_FRAMEBUFFER ||
				target == GL_DRAW_FRAMEBUFFER );

		glBindFramebuffer(target, 0);
	}

}